package faf.business;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;

import faf.entities.Account;
import faf.services.MailService;

public class UserManager extends BaseManager {

	private String seed = "d03c2015bcc148276834f66276e76a18aa87f8f0"; // random
																		// seed
																		// ;)

	private MailService mailService;

	public MailService getMailService() {
		return mailService;
	}

	public void setMailService(MailService mailService) {
		this.mailService = mailService;
	}

	/**
	 * Util method which generates a hash from a string
	 * 
	 */
	public String computeHash(String string) throws NoSuchAlgorithmException {
		MessageDigest m;
		byte[] data;
		byte[] seed_data = seed.getBytes();
		BigInteger i;

		// compute password hash
		m = MessageDigest.getInstance("MD5");
		data = string.getBytes();
		m.update(seed_data, 0, seed_data.length);
		m.update(data, 0, data.length);
		i = new BigInteger(1, m.digest());
		return String.format("%1$032X", i);
	}

	/**
	 * Permet de logger un utilisateur logger un utilisateur à partir d'un email
	 * et d'un mot de passe
	 * 
	 * @param email
	 * @param password
	 * @return
	 * @throws NoSuchAlgorithmException
	 *             renvoyé si il n'y pas de support MD5 sur la JVM
	 */
	public Account login(String email, String password)
			throws NoSuchAlgorithmException {
		MessageDigest m;
		byte[] data;
		byte[] seed_data = seed.getBytes();
		BigInteger i;

		m = MessageDigest.getInstance("MD5");
		data = password.getBytes();
		m.update(seed_data, 0, seed_data.length);
		m.update(data, 0, data.length);
		i = new BigInteger(1, m.digest());
		password = String.format("%1$032X", i);

		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();
			TypedQuery<Account> query = em.createQuery(
					"FROM Account a WHERE a.mail = ?1 AND a.password = ?2",
					Account.class);
			query.setParameter(1, email);
			query.setParameter(2, password);
			List<Account> accounts = query.getResultList();
			if (accounts.size() == 0) {
				return null;
			} else {
				Account account = accounts.get(0);
				return account;
			}
		} finally {
			if (em != null)
				em.close();
		}
	}

	/**
	 * Permet de logger un utilisateur à partir de jeton token
	 * 
	 * @param token
	 *            de l'utilisateur à connecté
	 * @return account de l'utilisateur
	 */
	public Account login(String token) {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();
			TypedQuery<Account> query = em.createQuery(
					"FROM Account a WHERE a.token = ?1", Account.class);
			query.setParameter(1, token);
			List<Account> accounts = query.getResultList();
			if (accounts.size() == 0) {
				return null;
			} else {
				Account account = accounts.get(0);
				account.getSheetList();
				account.getSurveyList();
				return account;
			}
		} finally {
			if (em != null)
				em.close();
		}
	}
	
	/**
	 * Permet de recuperer un Account à partir de son email.
	 * 
	 * @param email
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public Account findAccountByEmail(String email)
			throws NoSuchAlgorithmException {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();

			// check that there isn't already an account with same email adress
			TypedQuery<Account> query = em.createQuery(
					"FROM Account a WHERE a.mail = ?1", Account.class);

			
			query.setParameter(1, email);
			List<Account> accountWithSameEmail = query.getResultList();

			if (accountWithSameEmail.size() != 0) {
				return accountWithSameEmail.get(0);
			} else {
				return null;
			}
		}
		catch (Exception e ){
			
			return null;
		} finally {
			if (em != null)
				em.close();
		}
	}

	/**
	 * Permet de créer un compte.
	 * 
	 * Cette méthode protege le mot de passe à l'aide d'un 
	 * hash md5. Le jeton est généré dans cette methode.
	 * 
	 * @param account
	 * @return l'account crée, ou null si il existe déjà un account avec la même
	 *         adresse email
	 * @throws NoSuchAlgorithmException
	 */
	public Account createAccount(Account account)
			throws NoSuchAlgorithmException {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();

			// check that there isn't already an account with same email adress
			TypedQuery<Account> query = em.createQuery(
					"FROM Account a WHERE a.mail = ?1", Account.class);

			query.setParameter(1, account.getMail());
			List<Account> accountWithSameEmail = query.getResultList();

			if (accountWithSameEmail.size() != 0) {
				return null;
			}

			// it's ok let's create a new account
			account.setAdministrator(false);
			account.setRedactor(false);

			// compute token and password hash
			String token = computeHash((new Double(System.currentTimeMillis()))
					.toString());
			account.setToken(token);

			// compute password hash
			if(account.getPassword()!=null)
			{
			String password = computeHash(account.getPassword());
			account.setPassword(password);
			}
			
			// save
			em.getTransaction().begin();
			em.persist(account);
			em.getTransaction().commit();

			return account;
		} finally {
			if (em != null)
				em.close();
		}
	}

	/**
	 * Change le mot de passe de l'utilisateur ayant pour email @parem email et
	 * envoi un email de confirmation
	 * 
	 * @param email
	 * @return renvoie false si il n'existe pas d'account avec l'email spécifié
	 * @throws Exception
	 *             renvoie une exception si il ne peut pas envoyer d'email
	 *             (mauvaise configuration du serveur mail)
	 */
	public boolean resetPassword(String email) throws Exception {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();

			TypedQuery<Account> query = em.createQuery(
					"FROM Account a WHERE a.mail = ?1", Account.class);

			query.setParameter(1, email);
			List<Account> accounts = query.getResultList();

			if (accounts.size() == 0) {
				return false;
			}

			Account account = accounts.get(0);

			String newpassword = computeHash(
					(new Double(System.currentTimeMillis())).toString())
					.substring(0, 8);
			String title = "[FoireAuxFiches] Nouveau mot de passe";
			String body = "Bonjour \n\n" + "Votre nouveau mot de passe est : "
					+ newpassword;
			getMailService().sendMail(email, title, body);

			em.getTransaction().begin();
			account.setPassword(computeHash(newpassword));
			em.getTransaction().commit();
			return true;
		} finally {
			if (em != null)
				em.close();
		}
	}

	/**
	 * Recherche d'un compte à l'aide de son id passé en paramètre
	 * Pierre-Olivier Pignon le 9 mars 2011 à 16:45:02
	 * 
	 * @param id
	 * @return Account
	 * @throws RuntimeException
	 */
	@SuppressWarnings("finally")
	public Account findAccount(long id) {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();
			em.getTransaction().begin();
			Account a = em.find(Account.class, id);
			em.getTransaction().commit();
			return a;
		} catch (Exception e) {
			return null;

		} finally {
			em.close();

		}

	}

	/**
	 * Mise à jour d'un compte Pierre-Olivier Pignon le 9 mars 2011 à 16:49:23
	 * 
	 * @param account
	 * @return
	 * @throws NoSuchAlgorithmException
	 */
	public void updateAccount(Account account) throws NoSuchAlgorithmException {
		EntityManager em = null;
		try {
			em = jpautils.createEntityManger();
			em.getTransaction().begin();
			em.merge(account);
			em.getTransaction().commit();
		} finally {
			em.close();
		}
	}

	/**
	 * Méthode générique qui supprime une entité. Pierre-Olivier Pignon le 12
	 * mars 2011 à 11:31:24
	 * 
	 * @param <T>
	 * @param clazz
	 * @param id
	 */
	private <T> void delete(Class<T> clazz, long id) {
		EntityManager em = jpautils.createEntityManger();
		try {
			T entity = em.find(clazz, id);
			em.getTransaction().begin();
			em.remove(entity);
			em.getTransaction().commit();
		} finally {
			em.close();
		}
	}

	/**
	 * Méthode qui supprime un compte utilisateur Pierre-Olivier Pignon le 11
	 * mars 2011 à 23:39:15
	 * 
	 * @param account
	 * @throws NoSuchAlgorithmException
	 */
	@SuppressWarnings("finally")
	public void deleteAccount(long id) {
		delete(Account.class, id);
	}
}
